package pe.edu.pucp.cpnc.mm.renderer;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.net.URL;
import java.util.HashMap;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import pe.edu.pucp.cpnc.mm.bean.Building;
import pe.edu.pucp.cpnc.mm.bean.Entrance;
import pe.edu.pucp.cpnc.mm.bean.Map;
import pe.edu.pucp.cpnc.mm.bean.ObjectDetail;
import pe.edu.pucp.cpnc.mm.bean.Player;
import pe.edu.pucp.cpnc.mm.bean.TileDetail;

public class Renderer {
    
    public HashMap<String, BufferedImage> sprites = new HashMap<>();
    private static final float alpha = 0.4f;
    public static  int Anchura = 49;
    public static  int Altura = 28;
    public static  int offsetX;
    public static  int offsetY;
    public BufferedImage minimapTerrain = null;
    public BufferedImage minimap = null;
    private static final String SCALED_SPRITE_SUFFIX = "SCL";
    private static final int MAXIMUM_IMAGE_SIZE = 1000;
    private final GraphicsConfiguration gfx_config;
    
    public Renderer(){
        gfx_config = GraphicsEnvironment.
                getLocalGraphicsEnvironment().getDefaultScreenDevice().
                getDefaultConfiguration();
    }
	

    public BufferedImage loadImage(String fileName){
        URL url = getClass().getClassLoader().getResource(fileName);
        try {	
            BufferedImage img = ImageIO.read(url);
            return toCompatibleImage(img,img.getWidth(),img.getHeight());
        } catch (Exception e) {
            return null;
        }
    }
    
    public BufferedImage getSprite(String name){
        BufferedImage img = (BufferedImage)sprites.get(name);
        if (img == null){
            img = loadImage("res/" + name + ".png");
            sprites.put(name, img);
        }
        return img;
    }
    
    public BufferedImage getScaledImage(String originalName, String scaledName, int width, int height){
        BufferedImage scaledImg = getSprite(scaledName);
        if(scaledImg==null || scaledImg.getWidth()!=width || scaledImg.getHeight()!=height){
            BufferedImage originalImg = getSprite(originalName);
            scaledImg = toCompatibleImage(originalImg, width, height);
            if(sprites.containsKey(scaledName))
                sprites.remove(scaledName);
            sprites.put(scaledName, scaledImg);
            return scaledImg;
        }else{
            return scaledImg;
        }
    }
    
    private BufferedImage toCompatibleImage(BufferedImage image, int width, int height)
    {
        if (image.getColorModel().equals(gfx_config.getColorModel()) &&
                image.getHeight()==height && image.getWidth()==width)
                return image;

        BufferedImage new_image = gfx_config.createCompatibleImage(
                        width, height, image.getTransparency());
        
        Graphics2D g2d = (Graphics2D) new_image.getGraphics();

        g2d.drawImage(image, 0, 0,width,height, null);
        g2d.dispose();
        return new_image; 
    }
    
    public void printMapNew (Map map, Graphics oG, JPanel panel, int mouseX, int mouseY, pe.edu.pucp.cpnc.mm.bean.Object selObj, int selOr){
        if (map == null) return ;
        Graphics2D g = (Graphics2D)oG;
        g.setColor(Color.black);
        g.fillRect(0, 0, panel.getWidth(), panel.getHeight());
        
        renderTerrain(map, g, mouseX,mouseY,CameraPanController.cameraPanX,CameraPanController.cameraPanY, panel,1);
        
        printSelectionRectangle(map, g, mouseX, mouseY, selObj, selOr);
                
        for (int iAux = 0; iAux < 2 * map.getSizeY() - 1; iAux++) {
            for (int i = iAux, j = map.getSizeX() - 1; i >= 0 && j >= 0; i--, j--) {
                if (i < map.getSizeY()) {
                    
                    TileDetail currTile = map.getTiles()[i][j];
                    TileDetail currTileAux;
                    int i1,j1;

                    if (currTile.getObjInside() != null) {
                        if(currTile.getObjInside().getOrientation() % 2 == 0){
                            i1 = i - currTile.getObjInside().getObject().getSizeY() + 1;
                            j1 = j - currTile.getObjInside().getObject().getSizeX() + 1;
                        }else{
                            i1 = i - currTile.getObjInside().getObject().getSizeX() + 1;
                            j1 = j - currTile.getObjInside().getObject().getSizeY() + 1;
                        }
                        
                        if (i1 >= 0 && j1 >= 0 && map.getTiles()[i1][j1].getObjInside()!=null) {
                            currTileAux = map.getTiles()[i1][j1];
                        } 
                        else continue;
                    } 
                    else continue;
                    
                    if (currTileAux.getIsRoot() == 1 && 
                            currTileAux.getObjInside().isEqualTo(currTile.getObjInside())) {
                        
                        pe.edu.pucp.cpnc.mm.bean.Object currObj = currTile.getObjInside().getObject();
                        int orientation = currTile.getObjInside().getOrientation();
                        renderObject(i1,j1,currObj,orientation, g, panel, false);
                        
                    }
                }
            }
        }
        printObjectPreview(map, g, panel, mouseX, mouseY, selObj, selOr);        
    }
    
    private void drawLines(Graphics g, int topLeftCornerX, int topLeftCornerY, int botRightCornerX, int botRightCornerY, int sizeX, int sizeY){
        int distX1 = (botRightCornerX - topLeftCornerX) * sizeX / (sizeX+sizeY);
        int distX2 = (botRightCornerX - topLeftCornerX) * sizeY / (sizeX+sizeY);
        int distY1 = (botRightCornerY - topLeftCornerY) * sizeX / (sizeX+sizeY);
        int distY2 = (botRightCornerY - topLeftCornerY) * sizeY / (sizeX+sizeY);

        g.drawLine(topLeftCornerX, topLeftCornerY + distY1, topLeftCornerX + distX1, topLeftCornerY);
        g.drawLine(topLeftCornerX + distX1, topLeftCornerY, botRightCornerX, topLeftCornerY + distY2);
        g.drawLine(botRightCornerX, topLeftCornerY + distY2, topLeftCornerX + distX2, botRightCornerY);
        g.drawLine(topLeftCornerX + distX2, botRightCornerY, topLeftCornerX, topLeftCornerY + distY1);
    }
    
    private void printSelectionRectangle(Map map, Graphics g, int mouseX, int mouseY,
            pe.edu.pucp.cpnc.mm.bean.Object selObj, int orientation) {

        int j = mouseX, i = mouseY;

        Graphics2D g2d_1 = (Graphics2D) g;
        g2d_1.setColor(Color.blue);
        Stroke stroke = new BasicStroke(2f);
        g2d_1.setStroke(stroke);
      
        if (selObj != null && i >= 0 && j >= 0 && i < map.getSizeY() && j < map.getSizeX()) {

            int sizeX = selObj.getSizeX();
            int sizeY = selObj.getSizeY();
            if(orientation % 2 == 1){
                int aux = sizeX;
                sizeX = sizeY;
                sizeY = aux;
            }    
            boolean outOfBounds = false, occupied = false;
            for (int a = i; a < i + selObj.getSizeY(); a++) {
                for (int b = j; b < j + selObj.getSizeX(); b++) {
                    if (!(a >= 0 && a < map.getSizeY() && b >= 0 && b < map.getSizeX())) {
                        //System.out.println("El objeto esta fuera de los limites.");
                        outOfBounds = true;
                        continue;
                    }
                    if (map.getTiles()[a][b].getOccupied() == 1) {
                        //System.out.println("El tile esta ocupado.");
                        occupied = true;
                    }
                }
            }
            int topLeftCornerX = (j * Anchura / 2) + (i * Anchura / 2) + offsetX + CameraPanController.cameraPanX;
            int topLeftCornerY = (i * Altura / 2) - (j * Altura / 2) - (Altura / 2) * sizeX + offsetY + CameraPanController.cameraPanY;
            int botRightCornerX = topLeftCornerX + (Anchura / 2) * (sizeX + sizeY);
            int botRightCornerY = topLeftCornerY + (Altura / 2) * (sizeX+sizeY);
            if (!outOfBounds) {
                drawLines(g2d_1, topLeftCornerX, topLeftCornerY, botRightCornerX, botRightCornerY, sizeX, sizeY);
            }
        } else if (selObj == null && i >= 0 && j >= 0 && i < map.getSizeY() && j < map.getSizeX()) {
            if (mouseX == j && mouseY == i) {
                int topLeftCornerX = (j * Anchura / 2) + (i * Anchura / 2) + offsetX + CameraPanController.cameraPanX;
                int topLeftCornerY = (i * Altura / 2) - (j * Altura / 2) - (Altura / 2) + offsetY + CameraPanController.cameraPanY;
                int botRightCornerX = topLeftCornerX + Anchura;
                int botRightCornerY = topLeftCornerY + Altura;
                drawLines(g2d_1, topLeftCornerX, topLeftCornerY, botRightCornerX, botRightCornerY, 1, 1);
            }
        }
    }
    
    private void printObjectPreview(Map map, Graphics g, ImageObserver panel, int mouseX, int mouseY,
            pe.edu.pucp.cpnc.mm.bean.Object selObj, int selOr) {

        //hacer un for sobre los currTiles que ocupen el objeto sobre el mouse, y de eso depende
        //si se imprime con tonalidad rojiza
        int j = mouseX, i = mouseY;
        if (selObj != null && i >= 0 && j >= 0 && i < map.getSizeY() && j < map.getSizeX()) { 
            
            boolean outOfBounds = false, occupied = false;
            for (int a = i; a < i + selObj.getSizeY(); a++) {
                for (int b = j; b < j + selObj.getSizeX(); b++) {
                    if (!(a >= 0 && a < map.getSizeY() && b >= 0 && b < map.getSizeX())) {
                        //System.out.println("El objeto esta fuera de los limites.");
                        outOfBounds = true; continue;
                    }                    
                    if (map.getTiles()[a][b].getOccupied() == 1) {
                        //System.out.println("El tile esta ocupado.");
                        occupied = true; 
                    }
                }
            }
            
            Graphics2D g2d = (Graphics2D) g;
            Composite prevComp = g2d.getComposite();
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
            if (!outOfBounds && occupied) {
                renderObject(i, j, selObj, selOr, g2d, (JPanel)panel, true);
            } else if(!outOfBounds){         
                renderObject(i, j, selObj, selOr, g2d, (JPanel)panel, false);
            }
            g2d.setComposite(prevComp);

        }
    }
    
    /**
     * 
     * @param cornerX X coordinate of the top left image's corner to draw.
     * @param cornerY Y coordinate of the top left image's corner to draw.
     * @param width width of the image to draw.
     * @param height height of the image to draw.
     * @param panel panel where the bounds will be checked.
     * @return if the image will be printed or not.
     */
    private boolean withinScreen(int cornerX, int cornerY, int width, int height, JPanel panel){
        if(panel == null) return true;
        
        boolean withinXBounds = (offsetX-CameraPanController.cameraPanX)<=cornerX+width &&
                offsetX-CameraPanController.cameraPanX + panel.getWidth()> cornerX;
        boolean withinYBounds = (-offsetY-CameraPanController.cameraPanY)<=cornerY+height &&
                offsetY-CameraPanController.cameraPanY + panel.getHeight()> cornerY;
        return withinXBounds && withinYBounds;
    }
    
    public void renderTerrain(Map map,Graphics2D g,int mouseX,int mouseY, int camPanX, int camPanY,JPanel panel, float scale){
        for(int i=0;i<map.getSizeY();i++){
            for (int j=0;j < map.getSizeX() ;j++){
               renderTile(g, i, j,mouseX, mouseY
                       ,camPanX ,camPanY ,map, panel,scale);
            }
        }
    }
    
    private void renderTile(Graphics g, int i, int j, int mouseX, int mouseY, 
            int camPanX, int camPanY, Map map, JPanel panel, double scale){
        int anchura = (int)(Anchura*scale);
        int altura = (int)(Altura*scale);
        int offsetXScl = (int)(offsetX*scale);
        int offsetYScl = (int)(offsetY*scale);
        
        if(mouseX<0 || withinScreen((j *  anchura/2) + (i * anchura/2) + offsetXScl,
                (i * altura/2) - (j * altura/2) - altura/2 + offsetYScl, 
                anchura, altura,panel)){
            TileDetail currTile = map.getTiles()[i][j];
            
            String sprtName = currTile.getTile().getType();
            String sprtNameSCL = (scale==1)? sprtName: sprtName+SCALED_SPRITE_SUFFIX ;
            BufferedImage scaledTile = getScaledImage(sprtName,sprtNameSCL,anchura,altura);
  
            g.drawImage(scaledTile, (j *  anchura/2) + (i * anchura/2) + offsetXScl + camPanX, 
                        (i * altura/2) - (j * altura/2) - altura/2 + offsetYScl + camPanY,panel);

            if(mouseX == j && mouseY == i){   
                g.drawImage(getScaledImage("blueGrid","blueGrid",anchura,altura), 
                        (j *  anchura/2) + (i * anchura/2) + offsetXScl + camPanX, 
                        (i * altura/2) - (j * altura/2) - altura/2 + offsetYScl + camPanY,panel);
            }
        } 
    }
    
    private void renderObject(int i,int j, pe.edu.pucp.cpnc.mm.bean.Object obj, int orientation, Graphics g, JPanel panel, boolean tint){
        int sizeX = obj.getSizeX();
        int sizeY = obj.getSizeY();
        if(orientation % 2 == 1){
            int aux = sizeX;
            sizeX = sizeY;
            sizeY = aux;
        }
        int rigPad = obj.getPadding(pe.edu.pucp.cpnc.mm.bean.Object.PadCode.RIGHT);
        int lefPad = obj.getPadding(pe.edu.pucp.cpnc.mm.bean.Object.PadCode.LEFT);
        int width = (Anchura * (sizeX+sizeY)/2);
        String sprite = obj.getSpriteToRender(orientation);
        BufferedImage scaledImg = getSprite(sprite+SCALED_SPRITE_SUFFIX);
        double scaledY;
        if(scaledImg==null){
            scaledY = getSprite(sprite).getHeight()*(width+rigPad+lefPad)/getSprite(sprite).getWidth();
            scaledImg = getScaledImage(sprite
                    ,sprite+SCALED_SPRITE_SUFFIX, width+rigPad +lefPad 
                    ,(int)Math.round(scaledY));
        }
        else
            scaledY = scaledImg.getHeight();
        
        if(tint) scaledImg = tintImage(scaledImg, Color.red);
        
        int topLeftCornerX = (j * Anchura / 2) + (i * Anchura / 2) + offsetX + CameraPanController.cameraPanX;
        int topLeftCornerY = (i * Altura / 2) - (j * Altura / 2) + offsetY -(int)scaledY + Altura*sizeY/2 + CameraPanController.cameraPanY;
        int botRightCornerX = topLeftCornerX  + (Anchura * (sizeX+sizeY)/2);
        int botRightCornerY = topLeftCornerY + (int)scaledY;
        
        if(withinScreen(topLeftCornerX-CameraPanController.cameraPanX, topLeftCornerY-CameraPanController.cameraPanY, 
                botRightCornerX-topLeftCornerX, botRightCornerY-topLeftCornerY, panel)){
            g.drawImage(scaledImg, topLeftCornerX - lefPad, topLeftCornerY, null);
            if(obj instanceof Building) renderEntrances((Building)obj, i, j, orientation, g);
        }
        
    }
    
    private BufferedImage tintImage(BufferedImage img, Color color){
        
        BufferedImage img2 = toCompatibleImage(img, img.getWidth(), img.getHeight());
        Graphics g2 = img2.getGraphics();
        g2.drawImage(img, 0, 0, null);
        for (int x = 0; x < img2.getWidth(); x++) {
            for (int y = 0; y < img2.getHeight(); y++) {
                if ((img2.getRGB(x, y) >> 24) != 0x00) {
                    img2.setRGB(x, y, img2.getRGB(x, y) | color.getRGB());
                }
            }
        }
        return img2;
    }
    
    private void renderEntrances(Building obj, int i, int j, int orientation, Graphics g){
        Color currColor=g.getColor();
        g.setColor(Color.RED);
        for(Entrance ent : obj.getEntrances()){
            if(ent.getX() >= obj.getSizeX() || ent.getX() < 0){
                if(orientation % 2 == 0){
                    int length = (ent.getLength()-1)*ent.getX()/Math.abs(ent.getX());
                    int i1 = i + ent.getY() - ent.getWidth();
                    int i2 = i + ent.getY() + ent.getWidth();
                    renderIsometricArea(j+ent.getX(),i1,j+ent.getX() +length,i2,g);
                }else{
                    int length = (ent.getLength()-1)*ent.getY()/Math.abs(ent.getY());
                    int i1 = i + obj.getSizeX() - ent.getX() -1;
                    int j1 = j + ent.getY() - ent.getWidth();
                    int j2 = j + ent.getY() + ent.getWidth();
                    renderIsometricArea(j1,i1,j2,i1 + length,g);
                }                
            }else{
                if(orientation % 2 == 0){
                    int length = (ent.getLength()-1)*ent.getY()/Math.abs(ent.getY());
                    int i1 = i + ent.getY();
                    int j1 = j + ent.getX() - ent.getWidth();
                    int j2 = j + ent.getX() + ent.getWidth();
                    renderIsometricArea(j1,i1,j2,i1 + length,g);
                }else{
                    int length = -(ent.getLength()-1)*ent.getY()/Math.abs(ent.getY());
                    int i1 = i + ent.getX() - ent.getWidth();
                    int i2 = i + ent.getX() + ent.getWidth();
                    renderIsometricArea(j+obj.getSizeY()-ent.getY()-1,i1,j + obj.getSizeY()-ent.getY()+length -1,i2,g);
                }
            }
        }
        g.setColor(currColor);
    }
    
    private void renderIsometricArea(int j1, int i1, int j2, int i2, Graphics g){
        //TOP LEFT CORNER
        int c1I = Math.min(i1, i2);
        int c1J = Math.min(j1, j2);
        //TOP RIGHT CORNER
        int c2I = Math.min(i1, i2);
        int c2J = Math.max(j1, j2);
        //BOT RIGHT CORNER
        int c3I = Math.max(i1, i2);
        int c3J = Math.max(j1, j2);
        //BOT LEFT CORNER
        int c4I = Math.max(i1, i2);
        int c4J = Math.min(j1, j2);
        
        g.drawLine(calculateDrawPosition(true, c1I, c1J), calculateDrawPosition(false, c1I, c1J)
                ,calculateDrawPosition(true, c2I, c2J+1), calculateDrawPosition(false, c2I, c2J+1));
        
        g.drawLine(calculateDrawPosition(true, c2I, c2J+1), calculateDrawPosition(false, c2I, c2J+1)
                ,calculateDrawPosition(true, c3I+1, c3J+1), calculateDrawPosition(false, c3I+1, c3J+1));
        
        g.drawLine(calculateDrawPosition(true, c3I+1, c3J+1), calculateDrawPosition(false, c3I+1, c3J+1)
                ,calculateDrawPosition(true, c4I+1, c4J), calculateDrawPosition(false, c4I+1, c4J));
        
        g.drawLine(calculateDrawPosition(true, c4I+1, c4J), calculateDrawPosition(false, c4I+1, c4J)
                ,calculateDrawPosition(true, c1I, c1J), calculateDrawPosition(false, c1I, c1J));
    }
    
    public void renderInitMinimap(Map map, JPanel panel){
        int mapWidth = (map.getSizeX()+map.getSizeY())*Anchura/2;
        float scale = (float)MAXIMUM_IMAGE_SIZE/mapWidth;
        
        if(minimapTerrain==null){
            minimapTerrain = new BufferedImage(MAXIMUM_IMAGE_SIZE,MAXIMUM_IMAGE_SIZE,BufferedImage.TYPE_INT_ARGB);
            minimap =new BufferedImage(MAXIMUM_IMAGE_SIZE,MAXIMUM_IMAGE_SIZE,BufferedImage.TYPE_INT_ARGB); 
        }
        Graphics2D g = (Graphics2D)minimapTerrain.getGraphics();
        g.setColor(Color.black);
        g.fillRect(0, 0, minimapTerrain.getWidth(), minimapTerrain.getHeight());
        renderTerrain(map, g, -1, -1,0,0, panel,scale);
    }
    
    public void updateMinimapTile(Map map,int i, int j,JPanel panel){
        int mapWidth = (map.getSizeX()+map.getSizeY())*Anchura/2;
        float scale = (float)MAXIMUM_IMAGE_SIZE/mapWidth;
        if(i<map.getSizeY() && j<map.getSizeX() && i>=0 && j>=0){
            Graphics2D g = (Graphics2D)minimapTerrain.getGraphics();
            renderTile(g, i, j, -1, -1, 0, 0, map, panel, scale);
        }
    }
    
    public void updateMinimapOverlay(Map map, JPanel workPanel){
        int mapWidth = (map.getSizeX()+map.getSizeY())*Anchura/2;
        float scale = (float)MAXIMUM_IMAGE_SIZE/mapWidth;
        Graphics2D g = (Graphics2D)minimap.getGraphics();
        g.drawImage(minimapTerrain, 0, 0,null);
        renderMinimapUnits(g, map, scale);
        renderMinimapCameraBound(g, scale, workPanel);
    }
    
    public void renderMinimapUnits(Graphics2D g, Map map, double scale){
        int anchura = (int)(Anchura*scale);
        int altura = (int)(Altura*scale);
        int offsetXScl = (int)(offsetX*scale);
        int offsetYScl = (int)(offsetY*scale);
        Color oldColor = g.getColor();
        for(Player p : map.getPlayers()){
            g.setColor(new Color(p.getColor()));
            for(ObjectDetail obj : p.getObjectDetails()){
                g.fillRect((obj.getPosX() * anchura / 2) + (obj.getPosY() * anchura / 2) + offsetXScl, 
                        (obj.getPosY() * altura / 2) - (obj.getPosX()* altura / 2) + offsetYScl - altura*obj.getObject().getSizeY()/2, 
                        (anchura * (obj.getObject().getSizeX()+obj.getObject().getSizeY())/2), 
                        (altura * (obj.getObject().getSizeX()+obj.getObject().getSizeY())/2));
            }
        }
        g.setColor(oldColor);
    }
    
    public void renderMinimapCameraBound(Graphics2D g,float scale, JPanel workPanel){
        Color oldColor = g.getColor();
        Stroke oldStroke = g.getStroke();
        g.setStroke(new BasicStroke(8));
        g.setColor(Color.white);
        g.drawRect(-(int)(CameraPanController.cameraPanX*scale), 
                -(int)(CameraPanController.cameraPanY*scale),
                (int)(workPanel.getWidth()*scale), (int)(workPanel.getHeight()*scale));
        g.setColor(oldColor);
        g.setStroke(oldStroke);
    }
    
    private int calculateDrawPosition(boolean forX, int i, int j){
        if(forX) return (j * Anchura / 2) + (i * Anchura / 2) + offsetX + CameraPanController.cameraPanX;
        else return (i * Altura / 2) - (j * Altura / 2) + offsetY  + CameraPanController.cameraPanY;
    }
	
}